

; (function (w, doc) {
    const subscriber = new Dep()
    function reactive(vm, _get, _set) {
        const data = vm.$data
        Object.keys(data).forEach(k => {
            const value = data[k];
            Object.defineProperty(vm, k, {
                enumerable: true,
                configurable: true,
                get() {
                    _get(vm, k);
                    Dep.target && subscriber.addSub(Dep.target);
                    return data[k]
                },
                set(newVal) {
                    _set(k, newVal, value);
                    data[k] = newVal;
                    subscriber.notify();
                }
            })
        })
    }
    class Computed {
        constructor() {
            this.computedData = [];
        }
        addComputed(vm, computed, key) {

            const desc = Object.getOwnPropertyDescriptor(computed, key);
            const descFn = desc.value.get ? desc.value.get : desc.value;
            const value = descFn.call(vm);
            const get = descFn.bind(vm);
            const dep = this.collectDep(descFn)
            this.addComputedProps({
                get, value, key, dep
            })
            const dataItem = this.computedData.find(v => v.key === key);

            Object.defineProperty(vm, key, {
                get() {
                    Dep.target && subscriber.addSub(Dep.target);
                    return dataItem.value
                }
            })
        }
        addComputedProps(computedProps) {
            this.computedData.push(computedProps)
        }
        update(key, cb) {
            this.computedData.forEach(v => {
                const dep = v.dep;
                const _key = dep.find(k => k === key);
                if (_key) {
                    v.value = v.get();
                    subscriber.notify();
                    cb && cb(key)
                }
            })
        }
        collectDep(fn) {
            const matched = fn.toString().match(/this\.(.+?)/g);
            return matched.map(v => v.split('.')[1])
        }
    }
    class Vue {
        constructor(options) {
            const { el, data, methods, watch, computed } = options;
            this.$data = typeof data === 'function' ? data.call(this) : data;
            this.$el = doc.querySelector(el);
            this.eventPool = [];
            this.methods = methods;
            this.init(this, watch, computed)
        }
        init(vm, watch, computed) {
            this.initData(vm, watch, computed);
            this.initView(vm.$el);
            this.initEvent(vm.eventPool)

        }
        initData(vm, watch, computed) {
            reactive(vm, () => { }, (key, newVal, oldVal) => {
                if (newVal === oldVal) {
                    return
                }
                this.$computed(key);
                this.$watch(key, newVal, oldVal)
            })
            const computedIns = this.initComputed(vm, computed);
            const watchIns = this.initWatch(vm, watch);
            this.$computed = computedIns.update.bind(computedIns);
            this.$watch = watchIns.invoke.bind(watchIns)
        }
        initComputed(vm, computed) {
            const computedIns = new Computed()
            for (let k in computed) {
                computedIns.addComputed(vm, computed, k)
            }
            return computedIns
        }
        initWatch(vm, watch) {
            const watchIns = new Watch()
            for (let k in watch) {
                watchIns.addWatch(vm, watch, k)
            }
            return watchIns
        }
        initView(node) {
            const reg = /\{\{\s*(\S+)\s*\}\}/g;
            if (node.nodeType === 1) {
                const event = node.getAttribute('@click');
                if (event) {
                    this.eventPool.push({
                        dom: node,
                        type: 'click',
                        fn: this.methods[event],
                        key: event
                    })
                }

            } else if (node.nodeType === 3) {
                const text = node.textContent

                const execArr = reg.exec(text)
                if (execArr) {
                    const key = execArr[1];
                    const value = key.split('.').reduce((obj, k) => obj[k], this);
                    new Watcher(this, key, (newVal) => {
                        node.textContent = text.replace(reg, newVal)
                    })
                    node.textContent = text.replace(reg, value)
                }

            }



            const childNodes = node.childNodes;

            if (!childNodes.length) {
                return
            }

            childNodes.forEach(child => this.initView(child))
        }
        initEvent(eventPool) {
            if (!eventPool.length) {
                return
            }
            for (const v of eventPool) {
                v.dom.addEventListener(v.type, v.fn.bind(this), false)
            }
        }
    }

    w.Vue = Vue;
})(window, document)